package edu.columbia.cs.psl.vmvm.chroot; import java.io.File; import java.io.IOException; import java.net.MalformedURLException; import java.net.URI; import java.net.URISyntaxException; import java.net.URL; import java.net.URLClassLoader; import java.net.URLConnection; import java.net.URLStreamHandler; import java.nio.charset.Charset; import java.nio.file.CopyOption; import java.nio.file.DirectoryNotEmptyException; import java.nio.file.DirectoryStream; import java.nio.file.FileSystemException; import java.nio.file.FileSystems; import java.nio.file.FileVisitResult; import java.nio.file.FileVisitor; import java.nio.file.Files; import java.nio.file.OpenOption; import java.nio.file.Path; import java.nio.file.StandardCopyOption; import java.nio.file.attribute.BasicFileAttributes; import java.util.Arrays; import java.util.HashMap; import java.util.HashSet; import java.util.WeakHashMap; import edu.columbia.cs.psl.vmvm.Constants; import edu.columbia.cs.psl.vmvm.VMState; import edu.columbia.cs.psl.vmvm.org.objectweb.asm.tree.MethodInsnNode; public class ChrootUtils { private static HashMap<String, String> accessedPaths = new HashMap<>(); private static final boolean CHROOT_ENABLED = true; private static final boolean DEBUG_ALL_ACCESS = false; private static final boolean DEBUG_WRITES =false; private static final boolean FORCE_CHROOT = false; public static HashSet<String> writtenPaths = new HashSet<>(); static { reset(true); } public static void debugContextCL(ClassLoader cl) { System.err.println("Chrootdebugcl:" +cl); if(cl instanceof URLClassLoader) { System.err.println(Arrays.toString(((URLClassLoader)cl).getURLs())); } new Exception().printStackTrace(); } static boolean isInAWrittenPath(String file) { if(file.equals("/")) return false; if(writtenPaths.contains(file)) return true; else { return isInAWrittenPath(new File(file).getParent()); } } static WeakHashMap<File, File> fileToFile = new WeakHashMap<>(); public static String chrootCapture(String path, boolean isOutput) { if(isOutput || FORCE_CHROOT) { writtenPaths.add(new File(path).getAbsolutePath()); path = ensureSafePath(path); if(DEBUG_WRITES) System.out.println("Redirecting write to " + path); } else { if(isInAWrittenPath(new File(path).getAbsolutePath())) { if(DEBUG_WRITES) System.out.println("Redirecting write to " + path); return ensureSafePath(path); } } return path; } public static URLConnection chrootCapture(URLConnection urlConnection, boolean isOutput) { // System.err.println("URL: " + urlConnection.getURL()); // new Exception().printStackTrace(); if(urlConnection.getURL() != null && urlConnection.getURL().toString().startsWith("file://")){ if(isOutput || FORCE_CHROOT) { writtenPaths.add(new File(urlConnection.getURL().getFile()).getAbsolutePath()); URL url = ensureSafeURL(urlConnection.getURL()); if(DEBUG_WRITES) System.out.println("Redirecting write to " + url); try { return url.openConnection(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } } else { if(isInAWrittenPath(new File(urlConnection.getURL().getFile()).getAbsolutePath())) { URL url = ensureSafeURL(urlConnection.getURL()); if(DEBUG_WRITES) System.out.println("Redirecting write to " + url); try { return url.openConnection(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } } return urlConnection; } public static URL chrootCapture(URL url, boolean isOutput) { // System.err.println("URL: " + urlConnection.getURL()); // new Exception().printStackTrace(); if(url.toString().startsWith("file://")){ if(isOutput || FORCE_CHROOT) { writtenPaths.add(new File(url.getFile()).getAbsolutePath()); url = ensureSafeURL(url); if(DEBUG_WRITES) System.out.println("Redirecting write to " + url); return url; } else { if(isInAWrittenPath(new File(url.getFile()).getAbsolutePath())) { url = ensureSafeURL(url); if(DEBUG_WRITES) System.out.println("Redirecting write to " + url); return url; } } } return url; } public static File chrootCapture(File file, boolean isOutput) { if(isOutput || FORCE_CHROOT) { writtenPaths.add(file.getAbsolutePath()); File _file = ensureSafeFile(file); fileToFile.put(file, _file); if(DEBUG_WRITES) System.out.println("Redirecting write to " + _file); return _file; } else { if(isInAWrittenPath(file.getAbsolutePath())) { File _file = ensureSafeFile(file); fileToFile.put(file, _file); if(DEBUG_WRITES) System.out.println("Redirecting write to " + _file); return _file; } } return file; } public static File chrootCaptureINV(File file, boolean isOutput) { if(fileToFile.containsKey(file)) return fileToFile.get(file); return file; } public static Path chrootCapture(Path path, boolean isOutput) { if(isOutput || FORCE_CHROOT) { writtenPaths.add(path.toAbsolutePath().toString()); Path ret = ensureSafeFolder(path); if(DEBUG_WRITES) System.out.println("Redirecting write to " + ret); return ret; } else if(isInAWrittenPath(path.toAbsolutePath().toString())) return ensureSafeFolder(path); return path; } public static Path write(Path path, byte[] bytes, OpenOption... options) throws IOException { return Files.write(path, bytes, options); } public static Path write(Path path, Iterable<? extends CharSequence> lines, Charset cs, OpenOption... options) throws IOException { return Files.write(path, lines, cs, options); } public static void reset() { reset(false); } private static void reset(final boolean isInit) { Path chRootDir = FileSystems.getDefault().getPath(Constants.CHROOT_DIR); if (Files.exists(chRootDir) && !isInit) { try { final HashSet<Path> dirsToDelete = new HashSet<>(); Files.walkFileTree(chRootDir, new FileVisitor<Path>() { @Override public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException { return FileVisitResult.CONTINUE; } @Override public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { if(writtenPaths.contains(file) || isInit) { dirsToDelete.add(file.getParent()); Files.delete(file); } // else // System.out.println("SKipping deleting " + file); return FileVisitResult.CONTINUE; } @Override public FileVisitResult visitFileFailed(Path file, IOException exc) throws IOException { // try to delete the file anyway, even if its attributes // could not be read, since delete-only access is // theoretically possible Files.delete(file); return FileVisitResult.CONTINUE; } @Override public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException { if (exc == null) { if(dirsToDelete.contains(dir) || isInit) { try{ dirsToDelete.add(dir.getParent()); Files.delete(dir); } catch(DirectoryNotEmptyException ex) { //XXX nop } } return FileVisitResult.CONTINUE; } else { // directory iteration failed; propagate exception throw exc; } } }); accessedPaths.clear(); writtenPaths.clear(); try{ Files.createDirectory(chRootDir); } catch(Exception ex) { } } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } } else if(!Files.exists(chRootDir)) { try { Files.createDirectory(chRootDir); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } } // //Create a symlink to emulate cwd // Path cwd = new File(".").getAb.toPath(); // try { // Files.createLink(chRootDir.resolve(cwd), chRootDir.resolve(cwd.toAbsolutePath())); // } catch (IOException e) { // // TODO Auto-generated catch block // e.printStackTrace(); // } } private static synchronized void registerPathAccess(String path) { // new Exception().printStackTrace(); // System.out.println("ChrootUtils accessing: " + path); //System.out.println("Catalina.base: " + System.getProperty("catalina.base")); //if(System.getProperty("catalina.base") == null) // new Exception().printStackTrace(); if (!accessedPaths.containsKey(path)) { // System.out.println("ChrootUtils accessing: " + path); String chrootPath = path; if (!path.startsWith(Constants.CHROOT_DIR + "/")) { chrootPath = Constants.CHROOT_DIR + (path.startsWith("/") ? path : "/" + path); } else { path = chrootPath.substring(Constants.CHROOT_DIR.length() + 1); } accessedPaths.put(path, chrootPath); final Path existing = FileSystems.getDefault().getPath(path); final Path dest = FileSystems.getDefault().getPath(chrootPath); final Path chRootDirBase = FileSystems.getDefault().getPath(Constants.CHROOT_DIR); if (Files.exists(existing) && !Files.exists(dest)) { try { Files.createDirectories(dest.getParent()); if(DEBUG_WRITES) System.out.println("Copying " + path + " to " + chrootPath); Files.walkFileTree(existing, new FileVisitor<Path>() { @Override public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException { Path targetPath = dest.resolve(existing.relativize(dir)); if (!Files.exists(targetPath)) { Files.createDirectory(targetPath); } return FileVisitResult.CONTINUE; } @Override public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { try { Files.copy(file, dest.resolve(existing.relativize(file)), StandardCopyOption.COPY_ATTRIBUTES); } catch (FileSystemException ex) { Files.createSymbolicLink(dest.resolve(existing.relativize(file)), file); } return FileVisitResult.CONTINUE; } @Override public FileVisitResult visitFileFailed(Path file, IOException exc) throws IOException { return null; } @Override public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException { return FileVisitResult.CONTINUE; } }); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } } else if (Files.isDirectory(dest) && Files.exists(existing) && Files.exists(dest)) { try { if(DEBUG_WRITES) System.out.println("Copying " + path + " to " + chrootPath); Files.walkFileTree(existing, new FileVisitor<Path>() { @Override public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException { Path targetPath = dest.resolve(existing.relativize(dir)); if (!Files.exists(targetPath)) { Files.createDirectory(targetPath); } return FileVisitResult.CONTINUE; } @Override public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { try { Path targetPath = dest.resolve(existing.relativize(file)); if(!Files.exists(targetPath)) Files.copy(file, targetPath, StandardCopyOption.COPY_ATTRIBUTES); } catch (FileSystemException ex) { Files.createSymbolicLink(dest.resolve(existing.relativize(file)), file); } return FileVisitResult.CONTINUE; } @Override public FileVisitResult visitFileFailed(Path file, IOException exc) throws IOException { return null; } @Override public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException { return FileVisitResult.CONTINUE; } }); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } } public static String getCaptureMethodName(MethodInsnNode mi) { return "_" + mi.owner.replace("/", "_") + "_" + mi.name.replace("<", "_").replace(">", "_"); } public static String getCaptureInitParentMethodName(MethodInsnNode mi) { return "_parent_" + mi.owner.replace("/", "_") + "_" + mi.name.replace("<", "_").replace(">", "_"); } public static String ensureSafePath(String s, VMState state) { String path = new File(s).getAbsolutePath(); registerPathAccess(path); if (!path.startsWith(Constants.CHROOT_DIR + "/")) { path = Constants.CHROOT_DIR + (s.startsWith("/") ? s : "/" + s); } // System.out.println(path); return path; } public static String ensureSafePath(String s) { if(!CHROOT_ENABLED) return s; if(DEBUG_ALL_ACCESS) System.out.println("Accessing string-based file: " + s); if(s.contains("output/build/logs")) return s; String path = new File(s).getAbsolutePath(); registerPathAccess(path); if (!path.startsWith(Constants.CHROOT_DIR + "/")) { path = Constants.CHROOT_DIR + (path.startsWith("/") ? path : "/" + path); // System.out.println("I chrooted" + path); } // System.out.println("chrootutils returning " + path); return path; } public static Path ensureSafeFolder(Path f) { if(!CHROOT_ENABLED) return f; if(DEBUG_ALL_ACCESS) System.out.println("Accessing path: " + f); String path = f.toFile().getAbsolutePath(); registerPathAccess(path); if (!path.startsWith(Constants.CHROOT_DIR + "/")) { path = Constants.CHROOT_DIR + path; // System.out.println("Fudging temp folder" + path); Path r = FileSystems.getDefault().getPath(path); try { r = Files.createDirectories(r); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } return r; } return f; } public static File ensureSafeFile(File f) { if(!CHROOT_ENABLED) return f; if(DEBUG_ALL_ACCESS) System.out.println("Acessing: " + f); String path = f.getAbsolutePath(); if(path.contains("output/build/logs")) return f; registerPathAccess(path); if (!path.startsWith(Constants.CHROOT_DIR + "/")) { path = Constants.CHROOT_DIR + f.getAbsolutePath(); // System.out.println("Fudging temp file" + path); File r = new File(path); // try { // f.delete(); // r.createNewFile(); return r; // } catch (IOException e) { // // TODO Auto-generated catch block // e.printStackTrace(); // } } return f; } public static Object[] ensureSafeURLArrayReturn(String spec) throws MalformedURLException { if (spec != null && spec.startsWith("file://")) { URL ret = new URL(spec); registerPathAccess(ret.getPath()); if (!ret.getPath().startsWith(Constants.CHROOT_DIR + "/")) { return new Object[] { "file://" + Constants.CHROOT_DIR + "/" + ret.getPath() }; } } return new Object[] { (spec) }; } public static Object[] ensureSafeURLArrayReturn(String protocol, String host, int port, String file) throws MalformedURLException { if ("file".equals(protocol)) { registerPathAccess(file); if (!file.startsWith(Constants.CHROOT_DIR + "/")) { return new Object[] { protocol, host, port, Constants.CHROOT_DIR + "/" + file }; } } return new Object[] { protocol, host, port, file }; } public static Object[] ensureSafeURLArrayReturn(String protocol, String host, int port, String file, URLStreamHandler handler) throws MalformedURLException { if ("file".equals(protocol)) { registerPathAccess(file); if (!file.startsWith(Constants.CHROOT_DIR + "/")) { return new Object[] { protocol, host, port, Constants.CHROOT_DIR + "/" + file, handler }; } } return new Object[] { protocol, host, port, file, handler }; } public static Object[] ensureSafeURLArrayReturn(String protocol, String host, String file) throws MalformedURLException { if ("file".equals(protocol)) { registerPathAccess(file); if (!file.startsWith(Constants.CHROOT_DIR + "/")) { return new Object[] { protocol, host, Constants.CHROOT_DIR + "/" + file }; } } return new Object[] { protocol, host, file }; } public static Object[] ensureSafeURIArrayReturn(String spec) throws URISyntaxException { if (spec != null && spec.startsWith("file://")) { URI ret = new URI(spec); registerPathAccess(ret.getPath()); if (!ret.getPath().startsWith(Constants.CHROOT_DIR + "/")) { return new Object[] { "file://" + Constants.CHROOT_DIR + "/" + ret.getPath() }; } } return new Object[] { (spec) }; } public static Object[] ensureSafeURIArrayReturn(String scheme, String ssp, String fragment) throws URISyntaxException { if (scheme != null && scheme.startsWith("file://")) { URI ret = new URI(scheme, ssp, fragment); registerPathAccess(ret.getPath()); if (!ret.getPath().startsWith(Constants.CHROOT_DIR + "/")) { return new Object[] { scheme, Constants.CHROOT_DIR + ret.getRawSchemeSpecificPart(), fragment }; } } return new Object[] { scheme, ssp, fragment }; } public static Object[] ensureSafeURIArrayReturn(String scheme, String userInfo, String host, int port, String path, String query, String fragment) throws URISyntaxException { if (scheme != null && scheme.startsWith("file://")) { URI ret = new URI(scheme, userInfo, host, port, path, query, fragment); registerPathAccess(ret.getPath()); if (!ret.getPath().startsWith(Constants.CHROOT_DIR + "/")) { return new Object[] { scheme, userInfo, host, port, Constants.CHROOT_DIR + ret.getRawSchemeSpecificPart(), query, fragment }; } } return new Object[] { scheme, userInfo, host, port, path, query, fragment }; } public static Object[] ensureSafeURIArrayReturn(String scheme, String host, String path, String fragment) throws URISyntaxException { if (scheme != null && scheme.startsWith("file://")) { URI ret = new URI(scheme, host, path, fragment); registerPathAccess(ret.getPath()); if (!ret.getPath().startsWith(Constants.CHROOT_DIR + "/")) { return new Object[] { scheme, host, Constants.CHROOT_DIR + "/" + path, fragment }; } } return new Object[] { scheme, host, path, fragment }; } public static Object[] ensureSafeURIArrayReturn(String scheme, String authority, String path, String query, String fragment) throws URISyntaxException { if (scheme != null && scheme.startsWith("file://")) { URI ret = new URI(scheme, authority, path, query, fragment); registerPathAccess(ret.getPath()); if (!ret.getPath().startsWith(Constants.CHROOT_DIR + "/")) { return new Object[] { scheme, authority, Constants.CHROOT_DIR + "/" + path, query, fragment }; } } return new Object[] { scheme, authority, path, query, fragment }; } public static URL ensureSafeURL(String spec) throws MalformedURLException { if (spec != null && spec.startsWith("file://")) { URL ret = new URL(spec); registerPathAccess(ret.getPath()); if (!ret.getPath().startsWith(Constants.CHROOT_DIR + "/")) { return new URL("file://" + Constants.CHROOT_DIR + "/" + ret.getPath()); } } return new URL(spec); } public static URL ensureSafeURL(String protocol, String host, int port, String file) throws MalformedURLException { if ("file".equals(protocol)) { registerPathAccess(file); if (!file.startsWith(Constants.CHROOT_DIR + "/")) { return new URL(protocol, host, port, "/" + Constants.CHROOT_DIR + "/" + file); } } return new URL(protocol, host, port, file); } public static URL ensureSafeURL(String protocol, String host, int port, String file, URLStreamHandler handler) throws MalformedURLException { if ("file".equals(protocol)) { registerPathAccess(file); if (!file.startsWith(Constants.CHROOT_DIR + "/")) { return new URL(protocol, host, port, "/" + Constants.CHROOT_DIR + "/" + file, handler); } } return new URL(protocol, host, port, file, handler); } public static URL ensureSafeURL(String protocol, String host, String file) throws MalformedURLException { if ("file".equals(protocol)) { registerPathAccess(file); if (!file.startsWith(Constants.CHROOT_DIR + "/")) { return new URL(protocol, host, "/" + Constants.CHROOT_DIR + "/" + file); } } return new URL(protocol, host, file); } public static URI ensureSafeURI(String str) throws URISyntaxException { return ensureSafeURI(new URI(str)); } public static URI ensureSafeURI(String scheme, String ssp, String fragment) throws URISyntaxException { return ensureSafeURI(new URI(scheme, ssp, fragment)); } public static URI ensureSafeURI(String scheme, String userInfo, String host, int port, String path, String query, String fragment) throws URISyntaxException { return ensureSafeURI(new URI(scheme, userInfo, host, port, path, query, fragment)); } public static URI ensureSafeURI(String scheme, String host, String path, String fragment) throws URISyntaxException { return ensureSafeURI(new URI(scheme, host, path, fragment)); } public static URI ensureSafeURI(String scheme, String authority, String path, String query, String fragment) throws URISyntaxException { return ensureSafeURI(new URI(scheme, authority, path, query, fragment)); } public static URL ensureSafeURL(String spec, VMState state) throws MalformedURLException { if (spec != null && spec.startsWith("file://")) { URL ret = new URL(spec); registerPathAccess(ret.getPath()); if (!ret.getPath().startsWith(Constants.CHROOT_DIR + "/")) { return new URL("file://" + Constants.CHROOT_DIR + "/" + ret.getPath()); } } return new URL(spec); } public static URL ensureSafeURL(String protocol, String host, int port, String file, VMState state) throws MalformedURLException { if ("file".equals(protocol)) { registerPathAccess(file); if (!file.startsWith(Constants.CHROOT_DIR + "/")) { return new URL(protocol, host, port, "/" + Constants.CHROOT_DIR + "/" + file); } } return new URL(protocol, host, port, file); } public static URL ensureSafeURL(String protocol, String host, int port, String file, URLStreamHandler handler, VMState state) throws MalformedURLException { if ("file".equals(protocol)) { registerPathAccess(file); if (!file.startsWith(Constants.CHROOT_DIR + "/")) { return new URL(protocol, host, port, "/" + Constants.CHROOT_DIR + "/" + file, handler); } } return new URL(protocol, host, port, file, handler); } public static URL ensureSafeURL(String protocol, String host, String file, VMState state) throws MalformedURLException { if ("file".equals(protocol)) { registerPathAccess(file); if (!file.startsWith(Constants.CHROOT_DIR + "/")) { return new URL(protocol, host, "/" + Constants.CHROOT_DIR + "/" + file); } } return new URL(protocol, host, file); } public static URI ensureSafeURI(String str, VMState state) throws URISyntaxException { return ensureSafeURI(new URI(str), state); } public static URI ensureSafeURI(String scheme, String ssp, String fragment, VMState state) throws URISyntaxException { return ensureSafeURI(new URI(scheme, ssp, fragment), state); } public static URI ensureSafeURI(String scheme, String userInfo, String host, int port, String path, String query, String fragment, VMState state) throws URISyntaxException { return ensureSafeURI(new URI(scheme, userInfo, host, port, path, query, fragment), state); } public static URI ensureSafeURI(String scheme, String host, String path, String fragment, VMState state) throws URISyntaxException { return ensureSafeURI(new URI(scheme, host, path, fragment), state); } public static URI ensureSafeURI(String scheme, String authority, String path, String query, String fragment, VMState state) throws URISyntaxException { return ensureSafeURI(new URI(scheme, authority, path, query, fragment), state); } public static URI ensureSafeURI(URI uri, VMState state) { if (uri != null && uri.getScheme() != null && uri.getScheme().startsWith("file")) { File f = new File(uri); registerPathAccess(f.getAbsolutePath()); if (!f.getAbsolutePath().startsWith(Constants.CHROOT_DIR + "/")) { String path = Constants.CHROOT_DIR + f.getAbsolutePath(); return new File(path).toURI(); } } return uri; } public static URI ensureSafeURI(URI uri) { if (uri != null && uri.getScheme() != null && uri.getScheme().startsWith("file")) { File f = new File(uri); registerPathAccess(f.getAbsolutePath()); if (!f.getAbsolutePath().startsWith(Constants.CHROOT_DIR + "/")) { String path = Constants.CHROOT_DIR + f.getAbsolutePath(); return new File(path).toURI(); } } return uri; } public static URL ensureSafeURL(URL url) { try { return ensureSafeURI(url.toURI()).toURL(); } catch (MalformedURLException e) { e.printStackTrace(); } catch (URISyntaxException e) { e.printStackTrace(); } return null; } public static URL ensureSafeURL(URL url, VMState state) { try { return ensureSafeURI(url.toURI(), state).toURL(); } catch (MalformedURLException e) { e.printStackTrace(); } catch (URISyntaxException e) { e.printStackTrace(); } return null; } }